﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using Xceed.Silverlight.Data;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.DataGrid
{
  public class DataGridContext : DependencyObject
  {
    internal DataGridContext( IDataGridContextHelper dataGridContextHelper )
    {
      ObservableCollection<SelectionRange> selectedRangesObservableCollection = new ObservableCollection<SelectionRange>();
      this.SelectedRanges = selectedRangesObservableCollection;
      this.Columns = new ColumnCollection();
      m_dataGridContextHelper = dataGridContextHelper;
    }

    internal DataGridDataSource DataSource
    {
      get;
      set;
    }

    #region CurrentColumn Dependency Property
    public static readonly DependencyProperty CurrentColumnProperty = DependencyProperty.Register(
      "CurrentColumn",
      typeof( Column ),
      typeof( DataGridContext ),
      new PropertyMetadata( null, new PropertyChangedCallback( DataGridContext.OnCurrentColumnChanged ) ) );

    public Column CurrentColumn
    {
      get { return ( Column )this.GetValue( DataGridContext.CurrentColumnProperty ); }
      set { this.SetValue( DataGridContext.CurrentColumnProperty, value ); }
    }
    #endregion

    #region CurrentItem Dependency Property
    public static readonly DependencyProperty CurrentItemProperty = DependencyProperty.Register(
      "CurrentItem",
      typeof( object ),
      typeof( DataGridContext ),
      new PropertyMetadata( null, new PropertyChangedCallback( DataGridContext.OnCurrentItemChanged ) ) );

    public object CurrentItem
    {
      get { return ( object )this.GetValue( DataGridContext.CurrentItemProperty ); }
      set { this.SetValue( DataGridContext.CurrentItemProperty, value ); }
    }
    #endregion

    #region CurrentPath Dependency Property
    public static readonly DependencyProperty CurrentPathProperty = DependencyProperty.Register(
      "CurrentPath",
      typeof( DataGridPath ),
      typeof( DataGridContext ),
      new PropertyMetadata( null, new PropertyChangedCallback( DataGridContext.OnCurrentPathChanged ) ) );

    public DataGridPath CurrentPath
    {
      get { return ( DataGridPath )this.GetValue( DataGridContext.CurrentPathProperty ); }
      set { this.SetValue( DataGridContext.CurrentPathProperty, value ); }
    }
    #endregion

    #region FilterExpression Dependency Property
    public static readonly DependencyProperty FilterExpressionProperty = DependencyProperty.Register(
      "FilterExpression",
      typeof( FilterExpressionBase ),
      typeof( DataGridContext ),
      new PropertyMetadata( null, new PropertyChangedCallback( DataGridContext.OnFilterExpressionChanged ) ) );

    public FilterExpressionBase FilterExpression
    {
      get { return ( FilterExpressionBase )this.GetValue( DataGridContext.FilterExpressionProperty ); }
      set { this.SetValue( DataGridContext.FilterExpressionProperty, value ); }
    }
    #endregion

    #region SelectedItem Dependency Property

    public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
      "SelectedItem",
      typeof( object ),
      typeof( DataGridContext ),
      new PropertyMetadata( null, new PropertyChangedCallback( DataGridContext.OnSelectedItemChanged ) ) );
    public object SelectedItem
    {
      get { return ( object )this.GetValue( DataGridContext.SelectedItemProperty ); }
      set { this.SetValue( DataGridContext.SelectedItemProperty, value ); }
    }
    #endregion

    #region SelectedItems Property

    public SelectedItemCollection SelectedItems
    {
      get;
      internal set;
    }

    #endregion

    public ColumnCollection Columns
    {
      get;
      private set;
    }

    public ObservableCollection<GroupDescription> GroupDescriptions
    {
      get
      {
        return this.DataSource.GroupDescriptions;
      }
    }

    public IList<SelectionRange> SelectedRanges
    {
      get;
      private set;
    }

    public SortDescriptionCollection SortDescriptions
    {
      get
      {
        return this.DataSource.SortDescriptions;
      }
    }

    public StatContext StatContext
    {
      get
      {
        return null;
      }
    }

    #region IsUpdatingSharedProperty Property (Internal)

    private bool IsUpdatingSharedProperty
    {
      get;
      set;
    }

    #endregion

    public IAsyncResult BeginGetSelectedItems( AsyncCallback callback, object asyncState )
    {
      return this.BeginGetSelectedItems( this.SelectedRanges, callback, asyncState );
    }

    public IEnumerable<object> EndGetSelectedItems( IAsyncResult asyncResult )
    {
      if( asyncResult == null )
        throw new ArgumentNullException( "asyncResult" );

      SelectedItemsAsyncResult selectedItemsAsyncResult = asyncResult as SelectedItemsAsyncResult;

      if( selectedItemsAsyncResult == null )
        throw new ArgumentException( "The specified asyncResult must be a SelectedItemAsyncResult.", "asyncResult" );

      return selectedItemsAsyncResult.SelectedItems;
    }

    public void CollapseGroup( DataGridPath groupPath )
    {
      if( groupPath == null )
        return;

      int groupPathCount = groupPath.DataPath.Count;

      if( groupPathCount > this.GroupDescriptions.Count )
        throw new ArgumentException( "The specified DataGridPath contains a path longer than the number of available groups.", "groupPath" );

      groupPath.ActualDataPath = this.GetActualDataPathOrDataPath( groupPath );

      this.DataSource.CollapseGroup( groupPathCount - 1, groupPath.ActualDataPath );
    }

    public void ExpandGroup( DataGridPath groupPath )
    {
      if( groupPath == null )
        return;

      int groupPathCount = groupPath.DataPath.Count;

      if( groupPathCount > this.GroupDescriptions.Count )
        throw new ArgumentException( "The specified DataGridPath contains a path longer than the number of available groups.", "groupPath" );

      groupPath.ActualDataPath = this.GetActualDataPathOrDataPath( groupPath );

      this.DataSource.ExpandGroup( groupPathCount - 1, groupPath.ActualDataPath );
    }

    public bool IsGroupExpanded( DataGridPath groupPath )
    {
      if( groupPath == null )
        throw new ArgumentNullException( "groupPath" );

      groupPath.ActualDataPath = this.GetActualDataPathOrDataPath( groupPath );

      return this.IsGroupExpanded( groupPath.ActualDataPath );
    }

    internal bool IsGroupExpanded( DataPath groupPath )
    {
      if( groupPath == null )
        throw new ArgumentNullException( "groupPath" );

      int groupPathCount = groupPath.Depth;

      if( groupPathCount > this.GroupDescriptions.Count )
        throw new ArgumentException( "The specified DataGridPath contains a path longer than the number of available groups.", "groupPath" );

      return this.DataSource.IsGroupExpanded( groupPathCount - 1, groupPath );
    }

    internal int GetCollapsedDepth( DataPath path )
    {
      if( path == null )
        throw new ArgumentNullException( "path" );

      return this.DataSource.GetCollapsedDepth( path );
    }

    public void ToggleGroupExpansion( DataGridPath groupPath )
    {
      if( groupPath == null )
        return;

      int groupPathCount = groupPath.DataPath.Count;

      if( groupPathCount > this.GroupDescriptions.Count )
        throw new ArgumentException( "The specified DataGridPath contains a path longer than the number of available groups.", "groupPath" );

      groupPath.ActualDataPath = this.GetActualDataPathOrDataPath( groupPath );

      bool expanded = this.DataSource.IsGroupExpanded( groupPathCount - 1, groupPath.ActualDataPath );

      if( expanded )
      {
        this.DataSource.CollapseGroup( groupPathCount - 1, groupPath.ActualDataPath );
      }
      else
      {
        this.DataSource.ExpandGroup( groupPathCount - 1, groupPath.ActualDataPath );
      }
    }

    public UIElement GetContainerFromItem( object item )
    {
      return m_dataGridContextHelper.GetContainerFromItem( item );
    }

    public UIElement GetContainerFromPath( DataGridPath path )
    {
      return m_dataGridContextHelper.GetContainerFromPath( path );
    }

    public DataGridPath GetPathFromContainer( UIElement container )
    {
      return m_dataGridContextHelper.GetPathFromContainer( container );
    }

    public void BringColumnIntoView( Column column )
    {
      m_dataGridContextHelper.BringColumnIntoView( column );
    }

    public void BringItemIntoView( object item )
    {
      m_dataGridContextHelper.BringItemIntoView( item );
    }

    public void BringItemIntoView( DataGridPath dataGridPath )
    {
      m_dataGridContextHelper.BringItemIntoView( dataGridPath );
    }

    public void BringItemIntoView( object item, Column column, bool updateCurrent, bool updateSelection )
    {
      m_dataGridContextHelper.BringItemIntoView( item, column, updateCurrent, updateSelection );
    }

    public void BringItemIntoView( DataGridPath dataGridPath, Column column, bool updateCurrent, bool updateSelection )
    {
      m_dataGridContextHelper.BringItemIntoView( dataGridPath, column, updateCurrent, updateSelection );
    }

    public DataGridPath GetPathFromItem( object item )
    {
      return new DataGridPath( this.GetDataPathFromItem( item ) );
    }

    internal IAsyncResult BeginGetSelectedItem( SelectionRange selectionRange, AsyncCallback callback, object asyncState )
    {
      return this.BeginGetSelectedItems( new SelectionRange[] { selectionRange }, callback, asyncState );
    }

    internal IAsyncResult BeginGetSelectedItems( IEnumerable<SelectionRange> selectionRanges, AsyncCallback callback, object asyncState )
    {
      SelectedItemsAsyncResult asyncResult = new SelectedItemsAsyncResult( selectionRanges, callback, asyncState, this.DataSource );

      asyncResult.Launch();

      return asyncResult;
    }

    internal void SyncProperty( Action action )
    {
      if( this.IsUpdatingSharedProperty )
        return;

      this.IsUpdatingSharedProperty = true;

      try
      {
        action();
      }
      finally
      {
        this.IsUpdatingSharedProperty = false;
      }
    }

    internal DataPath GetActualDataPathOrDataPath( DataGridPath dataGridPath )
    {
      if( dataGridPath != null )
      {
        if( dataGridPath.ActualDataPath == null )
        {
          object[] items = new object[ dataGridPath.DataPath.Count ];
          dataGridPath.DataPath.CopyTo( items, 0 );

          dataGridPath.ActualDataPath = this.DataSource.CreateDataPathForArray( items );
        }
        return dataGridPath.ActualDataPath;
      }
      return null;
    }

    internal DataPath GetDataPathFromItem( object item )
    {
      if( item == null )
        return null;

      return this.DataSource.CreateDataPathForObject( item );
    }

    internal void UpdateSourceFilterExpression()
    {
      if( this.DataSource == null )
        return;

      List<FilterExpressionBase> filterExpressions = ( m_dataGridContextHelper != null )
        ? m_dataGridContextHelper.GetFilterRowsExpressions()
        : new List<FilterExpressionBase>();

      if( filterExpressions.Count > 0 )
      {
        if( this.FilterExpression != null )
        {
          filterExpressions.Add( this.FilterExpression );
        }

        this.DataSource.FilterExpression = ( filterExpressions.Count == 1 )
          ? filterExpressions[ 0 ]
          : new AndFilterExpression( filterExpressions );
      }
      else
      {
        this.DataSource.FilterExpression = this.FilterExpression;
      }
    }

    private static void OnCurrentColumnChanged( object sender, DependencyPropertyChangedEventArgs args )
    {
      DataGridContext dataGridContext = sender as DataGridContext;

      dataGridContext.SyncProperty( () => dataGridContext.m_dataGridContextHelper.CurrentColumn = ( Column )args.NewValue );
    }

    private static void OnCurrentItemChanged( object sender, DependencyPropertyChangedEventArgs args )
    {
      DataGridContext dataGridcontext = ( DataGridContext )sender;

      if( !object.Equals( args.NewValue, args.OldValue ) )
      {
        dataGridcontext.SyncProperty( () => dataGridcontext.m_dataGridContextHelper.CurrentItem = args.NewValue );
        DataGridPath newDataGridPath = ( args.NewValue != null ) ? dataGridcontext.GetPathFromItem( args.NewValue ) : null;
        dataGridcontext.SyncProperty( () => dataGridcontext.CurrentPath = newDataGridPath );
        dataGridcontext.SyncProperty( () => dataGridcontext.m_dataGridContextHelper.CurrentPath = newDataGridPath );
      }
    }

    private static void OnCurrentPathChanged( object sender, DependencyPropertyChangedEventArgs args )
    {
      DataGridContext dataGridcontext = ( DataGridContext )sender;
      DataGridPath datagridPath = ( DataGridPath )args.NewValue;

      if( !object.Equals( args.NewValue, args.OldValue ) )
      {
        dataGridcontext.SyncProperty( () => dataGridcontext.m_dataGridContextHelper.CurrentPath = datagridPath );
        object newCurrentItem = null;
        if( datagridPath != null )
        {
          datagridPath.ActualDataPath = dataGridcontext.GetActualDataPathOrDataPath( datagridPath );
          newCurrentItem = dataGridcontext.DataSource.DataHelper.ConvertBack( datagridPath.ActualDataPath );
        }
        dataGridcontext.SyncProperty( () => dataGridcontext.CurrentItem = newCurrentItem );
        dataGridcontext.SyncProperty( () => dataGridcontext.m_dataGridContextHelper.CurrentItem = newCurrentItem );
      }
    }

    private static void OnFilterExpressionChanged( object sender, DependencyPropertyChangedEventArgs args )
    {
      DataGridContext dataGridContext = ( DataGridContext )sender;

      FilterExpressionBase newFilter = args.NewValue as FilterExpressionBase;

      dataGridContext.SyncProperty( () => dataGridContext.m_dataGridContextHelper.FilterExpression = newFilter );

      if( ( newFilter != null ) && ( !newFilter.IsFrozen ) )
      {
        //Freeze will throw if new FilterExpression is not valid and cannot be frozen.
        newFilter.Freeze();
      }

      dataGridContext.UpdateSourceFilterExpression();
    }

    private static void OnSelectedItemChanged( object sender, DependencyPropertyChangedEventArgs args )
    {
      DataGridContext dataGridContext = ( DataGridContext )sender;

      dataGridContext.SyncProperty( () => dataGridContext.m_dataGridContextHelper.SelectedItem = args.NewValue );

    }

    IDataGridContextHelper m_dataGridContextHelper;
  }
}
